#!/usr/bin/php -q
<?
/****************************************************************************
 * (c) 2003 Jonathan Haskins <jhaskins@killobyte.com>
 * <place copyright notice here for each person who contributes to this file>
 *
 * TERMS OF USAGE:
 * This file is part of Web://CP.
 *
 * Web://CP is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 ****************************************************************************/

/* $Id: webcp-httpd.php,v 1.9 2007/02/28 23:10:58 gyrbo Exp $ */

/*
 * File:	webcp-httpd.php
 * Purpose:	Web://CP HTTP Daemon to server the panels
 * Creation:	2003-01-25
 */

// explicitly set error level
error_reporting(E_ALL & ~E_NOTICE);

/* change to the script directory since php CLI doesn't do it automatically */
chdir(dirname(__FILE__));

// include libs used for the server
include('../web/functions.inc.phps');
include('server.inc.phps');
include('webcp-httpd-functions.inc.phps');

// include all our front end libraries and classes

/* include configuration file */
include('../web/config.inc.php');
load_globals();
//set_error_handler('error_handler');

/* Make sure the php CLI/CGI has the needed modules */
if ((int)substr(preg_replace("/[^\d]*/", '', phpversion()), 0, 3) < 410)
 	$error[] = "PHP Error: You must be running PHP version 4.1 or higher.\n";
if (!extension_loaded('mysql'))
	$error[++$i] = "PHP Error: The MySQL module is required (configure PHP '--with-mysql=/usr')\n";
if (!extension_loaded('posix'))
	$error[] = "PHP Error: The POSIX module is required (configure PHP '--enable-posix')\n";
if (!extension_loaded('pcre'))
	$error[] = "PHP Error: The Perl Compatible Regular Expressions module is required (configure PHP '--with-pcre-regex')\n";
if (!extension_loaded('pcntl'))
	$error[] = "PHP Error: The Process Control module is required (configure PHP '--enable-pcntl')\n";
if (!extension_loaded('sockets'))
	$error[] = "PHP Error: The Sockets module is required (configure PHP '--enable-sockets')\n";
if (!ini_get('register_argc_argv'))
	$error[] = "Variable register_argc_argv must be set to On inside php.ini\n";
if ($cfg['httpd_mode'] != 'webcp')
	$error[] = "Configuration Error: The setting \$cfg['httpd_mode'] must be set to 'webcp'\n";
	
// Disable DB logging until the DB is configured
if (!file_exists($cfg['basedir'].'/config.php'))
{
	$configured = 0; 
	$cfg['dbloglevel'] = -1;
}
else
{
	$configured = 1;
}

	
if (isset($error)) {
	webcp_log(2, 0, 'webcp-httpd', implode("\n", $error), 0, true);
	exit;
}

// Check setup has run before connecting Database
if ($configured) {

	// Connect to the database
	$error = @mysql_connect($cfg['dbhost'], $cfg['dbuser'], $cfg['dbpass']);
	if (!$error)
	{
		webcp_log(0,0,'webcp.php',"Database connection failed, please check your config.php file.",0,1);
		exit;
	} 
	else 
	{
		@mysql_select_db($cfg['dbname']);
	}
}

/* Run from root only */
if (posix_getuid() != 0) {
	webcp_log(1, 0, 'webcp-httpd',"This program must be started as root (uid 0).\n", 0, true);
	exit;
}
/* Check if we are run interactively or with the -d switch; display to terminal if interactive */
$args = trim(next($_SERVER['argv']));
if ($args == '-d' || $args == '--daemon') {
	/* do not echo to terminal */
	$echo = false;
	define('INTERACTIVE', false);
} else {
	// output to standard out
	$echo = true;
	define('INTERACTIVE', true);

	if ($args == '-h' || $args == '--help') {
		webcp_log(3, 0, 'webcp-httpd', 'web-cp '.$cfg['webcp']." httpd daemon usage:
		webcp-httpd.php			(run interactively)
		webcp-httpd.php -i		(run interactively)
		webcp-httpd.php -d, --daemon	(run as daemon)
		webcp-httpd.php -h, --help	(display this help notice)\n\n", 0, true);
		exit;
	} else {
		webcp_log(3,0,'webcp-httpd',"\n\n Running web-cp ".$cfg['webcp']." httpd daemon".
			" interactively\n use ./webcp-httpd.php -h for help, CTRL-C to exit\n".
			"==================================================\n\n", 0, true);
	}
}
unset($args);

// Begin initial setup routines
if (!$configured)
{
	unset($error);

	$hostname = exec('hostname');

	if (!file_exists($cfg['basedir']))
	{
		$error[++$i] = "Base directory ".$cfg['basedir']." is incorrect\n"; 
	}
	
	// Create web-cp user and group
	$cfg['prog']['noshell'] = exec('/usr/bin/which false');
	if (!file_exists($cfg['prog']['noshell']))
	{
		$error[++$i] = "Unable to find 'false' command, please configure web-cp manually.\n";
	}
	
	$cfg['prog']['grep'] = exec('/usr/bin/which grep');
	if (!file_exists($cfg['prog']['grep']))
	{
		$error[++$i] = "Unable to find 'grep' command, please configure web-cp manually.\n";
	}
	
	// Check webcp httpd user does not exist before trying to add it
	exec($cfg['prog']['grep'].' '.$cfg['httpd_user'].' /etc/passwd', $output, $return_value);
	if ($return_value != 0)
	{
		$cfg['prog']['gadd'] = exec('/usr/bin/which groupadd');
	
		if (!file_exists($cfg['prog']['gadd']))
		{
			$error[++$i] = "Unable to find 'groupadd' command, please configure web-cp manually.\n";
		}
		else
		{
			exec($cfg['prog']['gadd'].' '.$cfg['httpd_group'], $output, $return_value);
			if ($return_value != 0)
			{
				$error[++$i] = "Unable to create web-cp httpd group, please configure web-cp manually.\n";
			}
		}
	
		$cfg['prog']['uadd'] = exec('/usr/bin/which useradd');
	
		if (!file_exists($cfg['prog']['uadd']))
		{
			$error[++$i] = "Unable to find 'useradd' command, please configure web-cp manually.\n";
		}
		else
		{
			exec($cfg['prog']['uadd']." -c 'web-cp httpd user account' -d ".$cfg['basedir']."/web -g ".$cfg['httpd_group']." -s ".$cfg['prog']['noshell']." ".$cfg['httpd_user'], $output, $return_value);
			if ($return_value != 0)
			{
				$error[++$i] = "Unable to create web-cp httpd user, please configure web-cp manually.\n";
			}
		}		
	}
	
	
	// If Process directory doesn't exist create it
	if (!file_exists('/var/run/webcp'))
	{
		if (!mkdir('/var/run/webcp', 0775))
		{
			$error[++$i] = "Failed to create process directory /var/run/webcp.\n";			
		}
	}
	
	// Setup permissions on process directory
	if (!chown('/var/run/webcp', $cfg['httpd_user']))
	{
		$error[++$i] = "Failed to change ownership on process directory /var/run/webcp.\n";
	}
	if (!chgrp('/var/run/webcp', $cfg['httpd_group']))
	{
		$error[++$i] = "Failed to change ownership on process directory /var/run/webcp.\n";
	}
	
	// Create log files
	if (!touch($cfg['logfile']))
	{
		$error[++$i] = "Failed to create logfile ".$cfg['logfile'].".\n";
	}

	if (!touch($cfg['accesslog']))
	{
		$error[++$i] = "Failed to create logfile ".$cfg['accesslog'].".\n";
	}

	// Setup permissions on log files
	if (!chown($cfg['logfile'], $cfg['httpd_user']))
	{
		$error[++$i] = "Failed to change ownership on log file ".$cfg['logfile'].".\n";
	}
	if (!chgrp($cfg['logfile'], $cfg['httpd_group']))
	{
		$error[++$i] = "Failed to change ownership on log file ".$cfg['logfile'].".\n";
	}

	if (!chown($cfg['accesslog'], $cfg['httpd_user']))
	{
		$error[++$i] = "Failed to change ownership on log file ".$cfg['accesslog'].".\n";
	}
	if (!chgrp($cfg['accesslog'], $cfg['httpd_group']))
	{
		$error[++$i] = "Failed to change ownership on log file ".$cfg['accesslog'].".\n";
	}

	if (isset($error))
	{
		webcp_log(0,0,'webcp.php',"Initial Setup routine failed. ".implode("\n",$error),0,1);
		exit;
	} 

	webcp_log(0,0,'webcp.php',"Setting up web-cp for the first time. Please load http://".$hostname.":".$cfg['port']." in a web browser to begin setup.",0,1);
}

/* get user uid and gid from username */
if (!($httpd_user = posix_getpwnam($cfg['httpd_user'])) || !($group = posix_getgrgid($httpd_user['gid']))) {
	webcp_log(0, 0, 'webcp-httpd', "User $cfg[httpd_user] does not exist.", 0, $echo);
	exit;
}

/* open a listening socket that is bound to an address and port */
if (!$lsocket = open_socket($cfg['ssl'] ? $cfg['ssl_port'] : $cfg['port'], $cfg['listen_address'], $cfg['listen_queue'])) {
	webcp_log(0, 0, 'webcp-httpd', 'Failed to create a listening socket because: '.socket_strerror(socket_last_error($lsocket)), 0, $echo);
	exit;
}

/* if ssl is on then start stunnel so we can accept ssl connections */
if ($cfg['ssl']) {
	switch ($cfg['osversion']) {
		case 'RedHat9.0':
	 		//spit out simple stunnel.conf file for redhat 9
	 		file_put_contents($cfg['basedir'].'/httpd/conf/stunnel4.conf', "######################################\n".
	 			"# this file is auto generated by webcp\n".
	 			"######################################\n\n".
	 			"# permissions on cert should be set to 0600\n".
	 			"cert = $cfg[ssl_cert]\n".
	 			"setuid = $httpd_user[name]\n".
	 			"setgid = $group[name]\n".
	 			"output = /var/log/secure\n".
	 			"pid = $cfg[httpsd_pid]\n\n".
	 			"[webcp]\n".
	 			"accept = $cfg[port]\n".
	 			"TIMEOUTclose = 0\n".
	 			"exec = $cfg[basedir]/server/ssl_in.php\n".
				"\n");

	 		$stunnel = $cfg['prog']['stunnel']." $cfg[basedir]/httpd/conf/stunnel4.conf 2>&1";
	 		break;
	 	default:
	 		$stunnel = $cfg['prog']['stunnel'].' '.
				"-p $cfg[ssl_cert] ".
	 			"-d $cfg[listen_address]:$cfg[port] ".
	 			"-l $cfg[basedir]/server/ssl_in.php ".
	 			"-s $httpd_user[name] ".
	 			"-g $group[name] ".
	 			"-P $cfg[httpsd_pid] ".
	 			"2>&1";
 	}

	/* run stunnel and exit on error */
	exec($stunnel, $stunnel_output, $stunnel_return);
	if ($stunnel_return !== 0) {
		webcp_log(0, 0, 'webcp-httpd', 'stunnel had a problem.', 0, $echo);
		http_stop();
	}
	webcp_log(2, 0, 'webcp-httpd', "stunnel is listening on port $cfg[port] and communicating with webcp through port $cfg[ssl_port]", 0, $echo);
}

/* change to a user with lower permissions than root. We should do this as soon as possible. */
posix_setgid($httpd_user['gid']);
posix_setuid($httpd_user['uid']);

/* define max length to use when reading from sockets/files */
define("MAX_LENGTH", 4096);
/* php 4.3 pcntl doesn't work without ticks declaration */
declare(ticks=1);
/* which functions should we run when we catch a posix signal
 * stop_server() before shutdown, or run reload_server() on SIGHUP */
pcntl_signal(SIGHUP, 'http_reload');	// sig# 1
pcntl_signal(SIGINT, 'http_stop');	// sig# 2
pcntl_signal(SIGTERM, 'http_stop');	// sig# 15

/* increase our memory limit to 16 megabytes (standard is 8M) */
ini_set('memory_limit', '16M');

/* CLI automatically sets time limit to unlimited, but we need this for CGI */
set_time_limit(0);

/* fork the script into a background process */
if (!INTERACTIVE) {
	$pid = pcntl_fork();
	if ($pid == -1) {
		webcp_log(0, 0, 'webcp-httpd', 'Failed to spawn a daemon', 0, true);
		exit;
	} elseif ($pid) {
		/* kill parent and return success */
		return 0;
	}

	/* become session leader / detach from the controlling terminal */
	if (!posix_setsid()) {
		webcp_log(0, 0, 'webcp-httpd',"Could not detach from terminal", 0, true);
		exit;
	} else {
		webcp_log(2, 0, 'webcp-httpd',"webcp-httpd daemon has been started.", 0, true);
	}
}

// update or create pid file
if (!set_pid_file(getmypid(), $cfg['httpd_pid'], $httpd_user['uid'])) {
	webcp_log(0, 0, 'webcp-httpd', 'Could not write pid file or server is already running', 0, $echo);
	exit;
}

// an array mapping short http status strings to long http strings
$http_status_map=array(
	"ok"			=> "200 OK",
	"status"		=> "200 OK",
	"redirect"		=> "302 Found",
	"notmodified"		=> "304 Not Modified",
	"forbidden"		=> "403 Forbidden",
	"not found"		=> "404 Not Found",
	"default"		=> "400 Bad Request"
);

/* set some initial status variables */
$httpd_status['started'] = get_microtime();
$httpd_status['hits'] = 0;
$httpd_status['connections'] = 0;
$httpd_status['sent'] = 0;

/* initialize the client socket array */
$csocket = array();

// Determine which server code to use
if ($cfg['httpd_prefork']) {
	$server_function = 'preforking_server';
} else {
	$server_function = 'run_server';
}

// start a daemon loop
$server_function('http_daemon', $cfg['max_clients'], $cfg['timeout'], $cfg['keep_alive'], $cfg['keep_alive_timeout'], $cfg['ssl']);

// we should never ever reach here
exit;
?>
